package com.nya.roulette;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
import java.util.Stack;

import com.gambling.roulette.R;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

/*
 * Class JDKDActivity
 	
 	Betting Strategy Simulator
 	
 	1, 1 1, 3 3

	 - winning on: 1st level = +1, 2nd level = 0, 3rd level = 0
	 - losing on 3rd level: best case (+1) = -8, worst case(0) = -9
	
	 until +2
	
	 1, 2 2, 3 3
	
	 -winning on: 1st level = +1, 2nd level = +1, 3rd level = -2
	 -losing on 3rd level: best case (+5) = -6, worst case (+2) = -9
	
	 until +6
	
	 1, 2 2
	
	 -winning on: 1st level = +1, 2nd level = +1
	 -losing on 2nd level: best case(+7) = +2, worst case(+6) = +1
	
	 until +8
	
	 1
	
	 -winning on: 1st level = +1
	 -losing (+8) = +7
	
	 until +9
	
	 <DONE/>
	
	 where...
	 numbers represent a multiplier of the initial bet (eg 1, 1 1, 3 3 with $25 = 25, 25 25, 75 75)
	 singles are any outside bet that pays out 1:1 (eg red / black, odd / even, high / low)
	 doubles are any two non-competing outside bet that pays out 2:1 (eg  rows or column groups)
	
	 Breakpoints: Spins Vs. Games
	
	 5/3 spins / game
	
	 -> +2, 4 games = 20/3 spins
	 -> +6, 24/5 games = 8 spins
	 -> +8, 12/5 games = 4 spins
	 -> +9, 1 game = 1 spin
 */
public class JDKDActivity extends Activity {

	private static final long serialVersionUID = 1L;

	// Possible states
	public enum State { ZERO, PLUS_TWO, PLUS_SIX, PLUS_EIGHT, END };
	
	// Layout areas that are playable
	public enum TableSpace { ONE_TO_18, EVEN, BLACK, RED, ODD, NINET_TO_36, FIRST_12, SECOND_12, THIRD_12, ROW_1, ROW_2, ROW_3 };
	
	// Instance of the entire game's stats and data structures.
	private GameState gameState;
    
	// Stack of game states (used for Undo levels)
	private Stack<GameState> gameStates;
	
	// Instance of relating the TableSpace to payout and location data.
	private Layout layout;
	
	// Layout of bets per "Game"
	// Reverse order because they are pushed onto a stack
	private final int betLayout[][][] = {	{ {3, 3}, {1, 1}, {1} }, // State 0
						  					{ {3, 3}, {2, 2}, {1} }, // State 2
						  					{ {2, 2}, {1} },		 // State 6
						  					{ {1} }					 // State 8
	};
	
	// Relates a state to a series of bets
	private HashMap<State, ArrayList<ArrayList<Integer>>> stateBet;
	
	// Labels
	private TextView chipCountLabel;
	private TextView ratingLabel;
	private TextView spinsLabel;
	private TextView gamesLabel;

    // Output strings
	private String chipCountString;
	private String ratingString;
	private String spinsString;
	private String gamesString;

    // Buttons
	private Button newGameButton;
	private Button loseButton;
	private Button winButton;
	private Button undoButton;
	
	// Background to draw chips on
	private ImageViewCanvas ivCanvas;
	
	// Dialogs
	private static final int DIALOG_QUIT = 0;
	private static final int DIALOG_ABOUT = 1;
	
	Dialog dialog;

	/** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        
        // Instantiate Game state
        gameState = new GameState();
        
        // Initialize stack of gameStates
        gameStates = new Stack<GameState>();
        
        // Layout data
        layout = new Layout();
        
        // Set of all possible states
    	Set<State> stateSet = EnumSet.allOf(State.class); 
    	
    	// Make sure the bet layout has the correct amount of states
    	// -1 because we do not count the 'END' state
    	assert(betLayout.length == stateSet.size() - 1);
        
        // Relate the states to the appropriate betting level
        stateBet = new HashMap<State, ArrayList<ArrayList<Integer>>>();
        Iterator<State> stateIterator = stateSet.iterator();
        for(int i = 0; i < betLayout.length; i++) {						// States
        	ArrayList<ArrayList<Integer>> betState = new ArrayList<ArrayList<Integer>>();
        	for(int j = 0; j < betLayout[i].length; j++) {				// Levels
        		ArrayList<Integer> betLevel = new ArrayList<Integer>();
        		for(int k = 0; k < betLayout[i][j].length; k++) {		// Bets
        			betLevel.add(betLayout[i][j][k]);
        		}
        		betState.add(betLevel);
        	}
        	if(stateIterator.hasNext())
        		stateBet.put(stateIterator.next(), betState);
        }
        
        // Labels
        chipCountLabel = (TextView)findViewById(R.id.chipCountTextView);
        ratingLabel = (TextView)findViewById(R.id.ratingTextView);
        spinsLabel = (TextView)findViewById(R.id.spinsTextView);
        gamesLabel = (TextView)findViewById(R.id.gamesTextView);

        // Output strings
        chipCountString = getString(R.string.chipCount);
        ratingString = getString(R.string.rating);
        spinsString = getString(R.string.spins);
        gamesString = getString(R.string.games);

        // Buttons
    	newGameButton = (Button)findViewById(R.id.newGameButton);
    	loseButton = (Button)findViewById(R.id.loseButton);
    	winButton = (Button)findViewById(R.id.winButton);
        undoButton = (Button)findViewById(R.id.undoButton);
        
        // Button listeners
        newGameButton.setOnClickListener(new NewGameButtonListener());
        loseButton.setOnClickListener(new LoseButtonListener());
        winButton.setOnClickListener(new WinButtonListener());
        undoButton.setOnClickListener(new UndoButtonListener());
        
        // Canvas
        ivCanvas = (ImageViewCanvas)findViewById(R.id.rouletteBackground);
        
        // Update stats
    	printStats();
    }
    /*
    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
		super.onSaveInstanceState(savedInstanceState);
		
		savedInstanceState.putSerializable("gameState", gameState);
		savedInstanceState.putSerializable("gameStates", gameStates);
		//savedInstanceState.putSerializable("ivCanvas", ivCanvas);
    }
    
    @SuppressWarnings("unchecked")
	@Override
    public void onRestoreInstanceState(Bundle savedInstanceState) {
		super.onRestoreInstanceState(savedInstanceState);

		gameState = (GameState)savedInstanceState.getSerializable("gameState");
		gameStates = (Stack<GameState>)savedInstanceState.getSerializable("gameStates");
		//ivCanvas = (ImageViewCanvas)savedInstanceState.getSerializable("ivCanvas");
		printStats();

			showButtons(true);

    }
   */
    // Class NewGameButtonListener
    // Initializes a new game when the user presses the "New Game" button
    private class NewGameButtonListener implements OnClickListener {

		public void onClick(View v) {
			
			// Initialize gameState
	    	gameState.init();
	    	
	    	// Initialize gameStates
	    	gameStates.clear();
	    	    
	    	// Load the initial series of bets
	        setBettingStrat();
	    	
	    	// Get the first bet
	        try {
	        	gameState.currentBet = gameState.bettingStrat.pop();
			} catch (EmptyStackException e) { // Should never happen
				gameState.state = State.END;
				e.printStackTrace();
				return;
			}
			
			// Show the buttons
			showButtons(true);
			
			// Hide Undo button
			undoButton.setVisibility(Button.INVISIBLE);
			
			// Print out the stats
	    	printStats();
			
	    	// Show us the first place to bid
			spin();
		}
    }
    
    // Class LoseButtonListener
    // Performs a lose action when the user presses the 'L' button
    private class LoseButtonListener implements OnClickListener {

		public void onClick(View v) {
				
			if(gameState.state == null || gameState.state == State.END)
				return;
			
			// Remember the previous gameState
			gameStates.push(gameState);
			
			// Start a new gameState as a copy of the previous
			gameState = new GameState(gameState);
			
			// Record the loss 
			updateStats(false);
						
			// Get the next bet
			// If there are no more bets, then end the game
			// OR
			// Special case
			// If the chip count is INITAL_CHIP_COUNT and we only have 1 bet left
			if(gameState.bettingStrat.size() == 0 || 
					(gameState.chipCount == gameState.INITAL_CHIP_COUNT && gameState.bettingStrat.size() == 1)) {

				// End of bets
				updateRatingOnTotalLoss();
				
				// Game is over
				gameState.state = State.END;
				walkAway();
			}
			else {	
				
				gameState.currentBet = gameState.bettingStrat.pop();
				// Still have bets
				// Show us the place to bid
				spin();
			}
			return;
		}
    }
    
    // Class WinButtonListener
    // Performs a win action when the user presses the 'W' button
    private class WinButtonListener implements OnClickListener {

		public void onClick(View v) {

			if(gameState.state == null || gameState.state == State.END)
				return;
			
			// Remember the previous gameState
			gameStates.push(gameState);
			
			// Start a new gameState as a copy of the previous
			gameState = new GameState(gameState);
			
			// Record the win 
			updateStats(true);
			
			// 1 in 18 games is a loss - play the odds
			if(gameState.games == 17) {
				gameState.state = State.END;
				walkAway();
				return;
			}
			
			// Advance through states
			switch(gameState.state) {
			case ZERO:
				if(gameState.rating == 2)
					gameState.state = State.PLUS_TWO;
				break;
			case PLUS_TWO:
				if(gameState.rating == 6)
					gameState.state = State.PLUS_SIX;
				if(gameState.rating < 2)
					gameState.state = State.ZERO;
				break;
			case PLUS_SIX:
				if(gameState.rating == 8)
					gameState.state = State.PLUS_EIGHT;
				break;
			case PLUS_EIGHT:
				gameState.state = State.END;
				walkAway();
				return;
			default:
				return;
			}
			
			// Always get a clean betting strat after a win
			setBettingStrat();
			
			// Get the next bet
			try {
				gameState.currentBet = gameState.bettingStrat.pop();
			} catch (EmptyStackException e) { // Should never happen
				gameState.state = State.END;
				e.printStackTrace();
				showButtons(false);
				return;
			}
			
			// Show us the place to bid
			spin();
		}
    }
    
    // Class UndoButtonListener
    // Initializes an undo (revert to previous game state) when the user presses the "Undo" button
    private class UndoButtonListener implements OnClickListener {

		public void onClick(View v) {
			
			// Only Undo if we can
			if(gameState.state == null || gameStates.size() == 0)
				return;
			
			// Use the last game state 
			gameState = new GameState(gameStates.pop());
			
			// Update the screen
			printStats();
			
			// Show buttons
			showButtons(true);
			
			// Hide Undo if we are at the first game state
			if(gameState.spins == 0)
				undoButton.setVisibility(Button.INVISIBLE);
			
			// Remove drawing of previous bets
	    	ivCanvas.removeChips();
	    	
	    	// Draw chip(s)
	    	Iterator<Chip> chipI = gameState.chipsOnLayout.iterator();
	    	while(chipI.hasNext())
				ivCanvas.addChip(chipI.next());
		}
    }
    
    // setBettingStrat
    // Given a state, puts a series of bets on the bettingStrat stack
    private void setBettingStrat() {

    	// Should not be trying to come up with a 
    	//  betting strategy when in END state. WALK AWAY!
    	assert(gameState.state != State.END);
    	
    	// Clean-up the stack
    	gameState.bettingStrat.clear();
    	
    	// Get the list of bets given the current state
    	ArrayList<ArrayList<Integer>> betState = stateBet.get(gameState.state);
    	
    	// Push the bets onto the bettingStrat
    	Iterator<ArrayList<Integer>> betStateIterator = betState.iterator();
    	while(betStateIterator.hasNext())
    		gameState.bettingStrat.push(betStateIterator.next());
    }
    
    // spin
    // Shows us where to bet.
    // 1st level betting - Choose from 1:1 payout spaces, single chip, single space.
    // 2nd and 3rd level betting - Choose from 2:1 payout spaces (with no overlapping numbers), many chips, two spaces.
    private void spin() {

    	// Allow Undo
    	if(gameState.spins > 0)
    		undoButton.setVisibility(Button.VISIBLE);
    	
    	// Remove drawing of previous bets
    	gameState.chipsOnLayout.clear();
    	ivCanvas.removeChips();
    	
    	// Set of all possible states
    	Set<TableSpace> tableSpaceSet = EnumSet.allOf(TableSpace.class);
    	
    	if(gameState.currentBet.size() == 1) { // Place bet on randomized 1:1 pay-out

    		// Remove all non-1:1 Payout options from the set
        	Iterator<TableSpace> tableSpaceI = tableSpaceSet.iterator();
        	while(tableSpaceI.hasNext()) {
        		TableSpace space = tableSpaceI.next();
        		if(layout.getPayout(space) != 1)
        			tableSpaceSet.remove(space);
        	}
        	
        	// Get a random space
        	Object[] tsArray = tableSpaceSet.toArray();
        	TableSpace layoutSpace = (TableSpace)tsArray[new Random().nextInt(tsArray.length)];
    		
        	// Add the chip to gameState layout
        	gameState.chipsOnLayout.add(new Chip(gameState.currentBet.get(0), layout.getLocation(layoutSpace)));
    	}
    	else {    					// Place 2 bets on 2:1 pay-out
    		
    		// Remove all non-2:1 Payout options from the set
    		Iterator<TableSpace> tableSpaceI = tableSpaceSet.iterator();
        	while(tableSpaceI.hasNext()) {
        		TableSpace space = tableSpaceI.next();
        		if(layout.getPayout(space) != 2)
        			tableSpaceSet.remove(space);
        	}
        	
    		if((new Random().nextInt(2)) == 0) {
    			// Remove column 2:1 Payout options from the set
    			tableSpaceSet.remove(TableSpace.FIRST_12);
    			tableSpaceSet.remove(TableSpace.SECOND_12);
    			tableSpaceSet.remove(TableSpace.THIRD_12);
    		}
    		else {
    			// Remove row 2:1 Payout options from the set
    			tableSpaceSet.remove(TableSpace.ROW_1);
    			tableSpaceSet.remove(TableSpace.ROW_2);
    			tableSpaceSet.remove(TableSpace.ROW_3);
    		}
    		
    		// Get a random space
        	Object[] tsArray = tableSpaceSet.toArray();
        	TableSpace layoutSpace = (TableSpace)tsArray[new Random().nextInt(tsArray.length)];
        	
        	// Remove it from the set
        	tableSpaceSet.remove(layoutSpace);
        	
        	// Add remaining chips to gameState layout
        	tableSpaceI = tableSpaceSet.iterator();
        	while(tableSpaceI.hasNext())
        		gameState.chipsOnLayout.add(new Chip(gameState.currentBet.get(0), layout.getLocation(tableSpaceI.next())));

    	}
    	// Draw chip(s)
    	Iterator<Chip> chipI = gameState.chipsOnLayout.iterator();
    	while(chipI.hasNext())
			ivCanvas.addChip(chipI.next());
    }
    
    // updateStats
    // Given a win or loss, update the stats accordingly
    // General location: stats may be changed in places other than updateStats
    private void updateStats(boolean win) {
    	
    	// Current stake
    	int loss = 0;
		for(int i = 0; i < gameState.currentBet.size(); i++) {
			loss += gameState.currentBet.get(i);
			}
		gameState.gameLoss += loss;
    	
    	if(win) {
    		// rating goes up by a multiple (either 1:1 or 2:1) of your current bet
    		// minus your current gameLoss
    		if(gameState.currentBet.size() == 1)	// 1:1 payout
    			gameState.rating = gameState.rating - gameState.gameLoss + 2 * gameState.currentBet.get(0);
    		else									// 2:1 payout
    			gameState.rating = gameState.rating - gameState.gameLoss + 3 * gameState.currentBet.get(0);
    		
    		gameState.games++;
    		gameState.gameLoss = 0;
    		gameState.chipCount = gameState.INITAL_CHIP_COUNT + gameState.rating;
    	}
    	else {
    		// loss - lose all stake
    		gameState.chipCount -= loss;
    	}
    	
    	gameState.spins++;
    	
    	printStats();
    }
    
    // updateRatingOnTotalLoss
    // Called only when a loss on the last bet in a level occurs
    private void updateRatingOnTotalLoss() {
    	gameState.rating -= gameState.gameLoss;
    	gameState.games++;
    	gameState.gameLoss = 0;
    	
    	printStats();
    }
    
    // printStats
    // Update the TextView's with the latest data.
    private void printStats() {
    	// Labels
    	chipCountLabel.setText(chipCountString + " " + Integer.toString(gameState.chipCount));
    	ratingLabel.setText(ratingString + " " + Integer.toString(gameState.rating));
    	spinsLabel.setText(spinsString + " " + Integer.toString(gameState.spins));
        gamesLabel.setText(gamesString + " " + Integer.toString(gameState.games));
    }
    
    // showButtons
    // Shows / Hides buttons on layout
    private void showButtons(boolean visible) {
    	
    	int visibleStatus;
    	
    	if(visible)
    		visibleStatus = Button.VISIBLE;
    	else
    		visibleStatus = Button.INVISIBLE;
    	
    	loseButton.setVisibility(visibleStatus);
    	winButton.setVisibility(visibleStatus);
    }
    
    // walkAway
    // Displays end game message
    // Hides buttons so the user cannot continue (with the exception of Undo)
    private void walkAway() {
    	if(gameState.rating > 0)
    		ratingLabel.setText(ratingString + " " + Integer.toString(gameState.rating) + " WIN! - WALK AWAY!");
    	else
    		ratingLabel.setText(ratingString + " " + Integer.toString(gameState.rating) + " LOSE! - WALK AWAY!");
    	showButtons(false);
    }
    
    /**
	 * onCreateDialog
	 * dialogs consisting of:
	 *  - menu selection quit prompt (DIALOG_QUIT)
	 *  - menu selection about prompt (DIALOG_ABOUT)
	 */
    @Override
    protected Dialog onCreateDialog(int id) {
        
        switch(id) {
        // Quit program
        case DIALOG_QUIT:
        	AlertDialog.Builder builder = new AlertDialog.Builder(this);
        	builder.setMessage("Are you sure you want to exit?");
        	builder.setCancelable(false);
        	// Quit Yes
        	builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
        		public void onClick(DialogInterface dialog, int id) {
    	        	   finish();
    	           }
    	       });
        	// Quit No
        	builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
        	           public void onClick(DialogInterface dialog, int id) {
        	        	   dialog.cancel();
        	           }
        	       });
        	dialog = builder.create();
            break;
        // About box
        case DIALOG_ABOUT:

    		dialog = new Dialog(this) {
    			
    			// Close the dialog if the screen is touched
    			@Override
    			public boolean onTouchEvent(MotionEvent event) {
    				switch (event.getAction()) {
    				case MotionEvent.ACTION_DOWN:
    					this.cancel();
    					break;
    				}
                    return true;
                }
    		};

        	dialog.setContentView(R.layout.about_dialog);
        	dialog.setTitle(R.string.about_title);

        	TextView text = (TextView) dialog.findViewById(R.id.text);
        	text.setText(R.string.about_message);
        	
            break;
        default:
            dialog = null;
        }
        return dialog;
    }
    
    /**
	 * onCreateOptionsMenu
	 * Creates of options in the menu (context click button on device)
	 */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.option_menu, menu);
        return true;
    }

    /**
	 * onOptionsItemSelected
	 * Presents the user with the options available when clicking the menu
	 */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        
    	switch (item.getItemId()) {
    	case R.id.about:
            // show about screen
        	showDialog(DIALOG_ABOUT);
            return true;
        case R.id.quit: 
        	// Exit application
            // Prompt are you sure ?
        	showDialog(DIALOG_QUIT);
        	
            return true;
        }
        
        return false;
    }
}